/******************************************************************************************************
Copyright (C) 2015 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals mainWindow, draw, nmr, settings, MessageBox, File, Dir, TextStream, NMRProcessing, ProcessingParametersReporter_Configuration */

/*jslint plusplus: true, indent: 4, continue:true, regexp: true, evil:true */

function ProcessingParametersReporter(aNMRProcessing, aDimCount, aCreateItem, aSpec, aLaunched) {
	"use strict";

	var processingParametersReporter;

	//*********************Main Program*********************

	//Check to execute once
	if (aLaunched !== undefined && aLaunched) {
		return;
	}
	processingParametersReporter = new ProcessingParametersReporter(aNMRProcessing, aDimCount, aCreateItem, aSpec, true);

	//Check for constructor parameters
	if (aNMRProcessing === undefined) {
		MessageBox.warning("In order to launch the script you must send an NMRProcessing object");
	} else {
		return processingParametersReporter.report(aNMRProcessing, aDimCount, aCreateItem, aSpec);
	}
}

function ManageReport(pathResult) {
	"use strict";

	this.resultDir = pathResult;

	ManageReport.prototype.writeToFile = function (dataLog, type) {
		var logFile, resultFile, fileExtension;

		switch (type) {
		case "text":
			fileExtension = ".txt";
			break;
		case "html":
			fileExtension = ".html";
			break;
		case "info":
			fileExtension = ".info";
			break;
		default:
			fileExtension = ".txt";
			break;
		}
		logFile = new File(new Dir(this.resultDir).filePath("results" + fileExtension));
		if (logFile.exists) {
			logFile.remove();
		}
		logFile.open(File.ReadWrite);
		resultFile = new TextStream(logFile);
		resultFile.codec =  "UTF-8";
		resultFile.write(dataLog);
		resultFile.flush();
		logFile.close();
	};
}

function Template(pathTemplate) {
	"use strict";

	this.templateFile = pathTemplate;
	this.positionsByElement = [];
	this.fieldItems = [];
	this.positionThreeEndTokens = [];

	Template.prototype.getItems = function (data) {
		var m, regexToFindTextBetweenKeys, result;

		//JavaScript do not have an option to make the dot match line break characters. .*? doesn't match new lines by default
		//We have to use [\s\S] to match any character. Since all characters are either whitespace or non-whitespace, this character class matches any character.
		regexToFindTextBetweenKeys = /\{\{[\S\s]*?\}\}/g;
		result = "";
		do {
			m = regexToFindTextBetweenKeys.exec(data);
			if (m) {
				this.positionsByElement.push({});
				this.positionsByElement[this.positionsByElement.length - 1].from = m.index;
				this.positionsByElement[this.positionsByElement.length - 1].to = regexToFindTextBetweenKeys.lastIndex;
				result += m.toString().replace(new RegExp("{{", 'g'), "");
			}
		} while (m);
		return result;
	};

	Template.prototype.readTemplate = function () {
		var stream, file;

		file = new File(this.templateFile);
		file.open(File.ReadOnly);
		stream = new TextStream(file);
		stream.codec =  "UTF-8";
		stream = stream.readAll();
		file.close();
		return stream;
	};

	Template.prototype.parseCSVToArray = function (aString, aFieldDelimiter, aRowDelimiter) {
		var i, j, arrData, maxColumns, auxColumns, maxRows, flag, position;

		//Initialize vars
		arrData  = [[]];
		maxColumns = 0;
		maxRows = 0;
		position = 0;

		//Get fields by iteration the string as if was an array.
		for (i = 0; i < aString.length - 1; i++) {

			//New column check
			flag = 0;
			for (j = 0; j < aFieldDelimiter.length; j++) {
				if (aString[i + j] === aFieldDelimiter[j]) {
					flag++;
				}
			}
			if (flag === aFieldDelimiter.length) {
				arrData[arrData.length - 1].push(aString.substring(position, i));
				position = i + 2;
				auxColumns++;
				i++;
				if (auxColumns >= maxColumns) {
					maxColumns++;
				}
			}

			//New row check
			flag = 0;
			for (j = 0; j < aRowDelimiter.length; j++) {
				if (aString[i + j] === aRowDelimiter[j]) {
					flag++;
				}
			}
			if (flag === aRowDelimiter.length) {
				if (auxColumns >= maxColumns) {
					maxColumns++;
				}
				arrData[arrData.length - 1].push(aString.substring(position, i));
				arrData.push([]);
				maxRows++;
				position = i + 2;
				i++;
				auxColumns = 0;
			}
		}

		return [arrData, maxColumns, maxRows];
	};

	Template.prototype.getPositionThreeEndTokens = function (aString) {
		var regex, m;

		regex = /\}\}\}/g;
		do {
			m = regex.exec(aString);
			if (m) {
				this.positionThreeEndTokens.push(m.index);
			}
		} while (m);
	};

	Template.prototype.removeComments = function (aSymbolForComment, aString) {
		var regex, m, aux, auxRegex;

		//Remove the whole line for each comment, including the new line realted to this comment
		//We try tu remove the line above. It doesn't exist we remove the line below.
		regex = new RegExp("^(\\t| )*" + aSymbolForComment + ".*", "m");
		do {
			m = regex.exec(aString);
			if (m) {
				auxRegex =  new RegExp("\r\n^(\\t| )*" + aSymbolForComment + ".*", "m");
				aux = aString.replace(auxRegex, "");
				if (aux.length === aString.length) {
					auxRegex =  new RegExp("^(\\t| )*" + aSymbolForComment + ".*\r\n", "m");
					aux = aString.replace(auxRegex, "");
					if (aux.length < aString.length) {
						aString = aux;
					} else {
						aString = aString.replace(regex, "");
					}
				} else {
					aString = aux;
				}
			}
		} while (m);

		return aString;
	};

	Template.prototype.getTemplateItems = function () {
		var file, data, arrayData, result;

		try {
			file = new File(this.templateFile);
			if (!file.exists) {
				MessageBox.critical("The Template file does not exists at this path:\n" + this.templateFile);
			} else {
				//We make the scond replacement in order to avoid getParams and getParameters execution problems
				this.rawData = this.readTemplate();
				this.rawData = this.removeComments("##", this.rawData);
				this.rawData = this.rawData.replace(/"/g, "'");
				this.rawData = this.rawData.replace(/&nbsp;;;/g, '\u00a0;;');
				data = this.getItems(this.rawData);
				arrayData = this.parseCSVToArray(data, ";;", "}}");
				this.fieldItems = arrayData[0];
				this.maxColumnsFieldItems = arrayData[1];
				this.maxRowsFieldItems = arrayData[2];
				this.getPositionThreeEndTokens(this.rawData);
				result = true;
			}
		} catch (err) {
			result = false;
		} finally {
			return result;
		}
	};
}

//-------------------INSTANCE CLASS METHODS-------------------------------
ProcessingParametersReporter.prototype.isRealNumber = function (aValue) {
	"use strict";

	var result;

	result = false;
	if (typeof (aValue) !== "boolean" && !isNaN(aValue)) {
		result = !(aValue % 1 === 0);
	}

	return result;
};

ProcessingParametersReporter.prototype.getDataOuterSymbols = function (aExpresion, aOpenSymbol, aCloseSymbol) {
	"use strict";

	var i, charArray, counterOpenSymbol, posOpenOuterSymbol, result;

	result = [];
	charArray = aExpresion.split('');
	counterOpenSymbol = 0;
	for (i = 0; i < aExpresion.length; i++) {
		if (charArray[i] === aOpenSymbol) {
			if (counterOpenSymbol === 0) {
				posOpenOuterSymbol = i + 1;
			}
			counterOpenSymbol++;
		} else if (charArray[i] === aCloseSymbol) {
			counterOpenSymbol--;
			if (counterOpenSymbol === 0) {
				result.push({});
				result[result.length - 1].data = aExpresion.substring(posOpenOuterSymbol, i);
				if ((posOpenOuterSymbol - 7 >= 0) && (aExpresion.substring(posOpenOuterSymbol - 7, posOpenOuterSymbol - 1) === "script")) {
					result[result.length - 1].scriptStatement = true;
				} else {
					result[result.length - 1].scriptStatement = false;
				}
			}
		}
	}
	return result;
};

ProcessingParametersReporter.prototype.checkCondition = function (aParameter, aNMRProcessing, aSpec) {
	"use strict";

	var result, i, dataBetweenParenthesis, aux;

	try {
		if (aParameter === undefined || aParameter === "") {
			result = true;
		} else if (aParameter !== undefined && (aParameter === true || aParameter === "true" || aParameter === 1)) {
			result = true;
		} else if (aParameter !== undefined && (aParameter === false || aParameter === "false" || aParameter === 0)) {
			result = false;
		} else if (aParameter !== undefined && aParameter !== "") {
			if (aParameter.indexOf("script") > -1) {
				result = this.processGetParameter(aParameter, aNMRProcessing, aSpec);
				dataBetweenParenthesis = this.getDataOuterSymbols(result, "(", ")");
				for (i = 0; i < dataBetweenParenthesis.length; i++) {
					if (dataBetweenParenthesis[i].scriptStatement) {
						aux = eval(dataBetweenParenthesis[i].data);
						result = this.replaceAll(result, "script(" + dataBetweenParenthesis[i].data + ")", aux);
					}
				}
				if (result === 1 || result === true || result === "true") {
					result = true;
				} else {
					result = false;
				}
			} else {
				result = aNMRProcessing.getParameter(aParameter);
			}
		}
	} catch (err) {
		result = "Error found at: " + result;
	} finally {
		return result;
	}
};

ProcessingParametersReporter.prototype.replaceAll = function (aString, aToken, aNewToken) {
	"use strict";

	var index = 0;

	do {
		aString = aString.replace(aToken, aNewToken);
	} while ((index = aString.indexOf(aToken, index + 1)) > -1);

	return aString;
};

ProcessingParametersReporter.prototype.executeRegularExpresion = function (aRegex, aExpresion, aToken, aNewToken, aNMRProcessing, aSpec) {
	"use strict";

	var m, expresion, result;

	result = aExpresion;
	try {
		if (aNMRProcessing !== undefined || aSpec !== undefined) {
			do {
				m = aRegex.exec(aExpresion);
				if (m) {
					expresion = m.toString().replace(aToken, aNewToken);
					result = this.replaceAll(result, m, expresion);
				}
			} while (m);
		}

	} catch (err) {
		result = "Error found at: " + result;
	} finally {
		return result;
	}
};

ProcessingParametersReporter.prototype.processGetParameter = function (aExpresion, aNMRProcessing, aSpec) {
	"use strict";

	var result;

	result = aExpresion;
	result = this.executeRegularExpresion(/__SPEC__.getParam\('.[^)]*\'\)/g, aExpresion, "__SPEC__", "aSpec", aNMRProcessing, aSpec);
	result = this.executeRegularExpresion(/__PROC__.getParameter\('.[^)]*\'\)/g, result, "__PROC__", "aNMRProcessing", aNMRProcessing, aSpec);

	return result;
};

ProcessingParametersReporter.prototype.getValue = function (aExpresion, aPrecision, aNMRProcessing, aSpec) {
	"use strict";

	var i, result, expresion, dataBetweenParenthesis, aux;

	try {
		result = "";
		expresion = aExpresion;
		if (expresion !== undefined && expresion !== "") {
			if (expresion.indexOf("script") > -1) {
				result = this.processGetParameter(expresion, aNMRProcessing, aSpec);
				dataBetweenParenthesis = this.getDataOuterSymbols(result, "(", ")");
				for (i = 0; i < dataBetweenParenthesis.length; i++) {
					if (dataBetweenParenthesis[i].scriptStatement) {
						aux = eval(dataBetweenParenthesis[i].data);
						result = this.replaceAll(result, "script(" + dataBetweenParenthesis[i].data + ")", aux);
					}
				}
			} else {
				result = aNMRProcessing.getParameter(expresion);
			}
		}
	} catch (err) {
		result = "Error found at: " + result;
	} finally {
		if (!isNaN(aPrecision)) {
			if (this.isRealNumber(result)) {
				result = Number(result).toFixed(aPrecision);
			}
		}
	}

	return result;
};

ProcessingParametersReporter.prototype.processTemplate = function (aNMRProcessing, aSpec, aTemplateFile, aIsHTML) {
	"use strict";

	var i, j, template, indexStringPosition, finalStringPosition, condition, value, lastNewLine,
		textBefore, precision, textAfter, macrosResult, macrosFinalResult, lastMacroNotExecuted;

	//Initialize vars and check if the template items has been processed fine.
	template = new Template(aTemplateFile);
	if (!template.getTemplateItems()) {
		return;
	}
	indexStringPosition = 0;
	finalStringPosition = template.positionsByElement[0].from;
	macrosFinalResult = "";

	//Process each template item row
	for (i = 0; i < template.maxRowsFieldItems; i++) {

		//Check conditions in order to know if we have to apply the macro
		condition = this.checkCondition(template.fieldItems[i][0], aNMRProcessing, aSpec);

		//Process and show the macro only if the condition is true
		if (condition) {

			//Get values from CSV
			textBefore = template.fieldItems[i][1];
			value = template.fieldItems[i][2];
			precision = template.fieldItems[i][3];
			textAfter = template.fieldItems[i][4];
			lastMacroNotExecuted = false;

			//Manage possible missed values
			if (textBefore === undefined) {
				textBefore = "";
			}
			if (value === undefined) {
				value = "";
			}
			if (precision === undefined) {
				precision = "";
			}
			if (textAfter === undefined) {
				textAfter = "";
			}

			//Evaluate the macro
			macrosResult = textBefore  + this.getValue(value, precision, aNMRProcessing, aSpec) + textAfter;

		} else {
			for (j = 0; j < template.positionThreeEndTokens.length; j++) {
				if (template.positionThreeEndTokens[j] + 2 === template.positionsByElement[i].to) {
					template.positionThreeEndTokens.splice(j, 1);
					template.positionsByElement[i].to++;
					j--;
				}
			}
			macrosResult = "";
			lastMacroNotExecuted = true;
		}

		//We create a new file by using the raw data and macro result evaluation
		macrosFinalResult += template.rawData.substring(indexStringPosition, finalStringPosition);
		indexStringPosition = template.positionsByElement[i].to;
		if (i < template.maxRowsFieldItems - 1) {
			finalStringPosition = template.positionsByElement[i + 1].from;
		} else {
			finalStringPosition = template.rawData.length;
			macrosResult += template.rawData.substring(indexStringPosition, finalStringPosition);
		}

		//Delete the blank lines related to the macros not executed. This avoid having a lot of blank lines into the HTML template
		if (lastMacroNotExecuted && aIsHTML) {
			lastNewLine = macrosFinalResult.substring(macrosFinalResult.length - 1, macrosFinalResult.length);
			if (lastNewLine === "\n") {
				macrosFinalResult = macrosFinalResult.substring(0, macrosFinalResult.length - 2);
			}
		}
		macrosFinalResult += macrosResult;

	}

	return macrosFinalResult;
};

ProcessingParametersReporter.prototype.getDefaultTemplates = function (aConfigurationObject, aDimension) {
	"use strict";

	var dataTemplates, currentTemplate;

	dataTemplates = aConfigurationObject.getDataTemplates(aDimension);
	currentTemplate = aConfigurationObject.getDefaultTemplateIndex(aDimension);

	return [dataTemplates, currentTemplate];
};

ProcessingParametersReporter.prototype.checkHTMLCode = function (aString) {
	"use strict";

	var regex = /<\/?(?:body|html|meta|title)(?:(?: [^<>]*)>|>?)/;

	return regex.test(aString);
};

ProcessingParametersReporter.prototype.report = function (aNMRProcessing, aDimCount, aCreateItem, aSpec) {
	"use strict";

	var result, macrosResult, templateFile, textItem, current1DTemplateKey, current2DTemplateKey, currentDataTemplatesKey, isHTML,
		dataTemplates, currentTemplate, processingParametersReporter_Configuration, flagErrorsSettings, resultDefaultTemplates;

	//Check for mandatory parameters
	if (aNMRProcessing === undefined || aDimCount === undefined) {
		return;
	}

	//Registry Entries to take information since configuration script
	current1DTemplateKey = "Processing Parameters Report/Current 1D Template";
	current2DTemplateKey = "Processing Parameters Report/Current 2D Template";
	currentDataTemplatesKey = "Processing Parameters Report/Data Templates";

	//Load registy information
	if (aDimCount === 1) {
		currentTemplate = settings.value(current1DTemplateKey, -1);
	} else if (aDimCount === 2) {
		currentTemplate = settings.value(current2DTemplateKey, -1);
	}
	dataTemplates = settings.value(currentDataTemplatesKey, "");
	if (dataTemplates !== "") {
		dataTemplates = JSON.parse(dataTemplates);
	}

	flagErrorsSettings = false;
	processingParametersReporter_Configuration = new ProcessingParametersReporter_Configuration();
	resultDefaultTemplates = [];

	//Manage possible problems by getting default templates
	if (aDimCount === 1 && (dataTemplates.length  === undefined || dataTemplates.length < 1 || currentTemplate === -1)) {
		resultDefaultTemplates = this.getDefaultTemplates(processingParametersReporter_Configuration, 1);
		flagErrorsSettings = true;
	}
	if (aDimCount === 2 && (dataTemplates.length  === undefined || dataTemplates.length < 1 || currentTemplate === -1)) {
		resultDefaultTemplates = this.getDefaultTemplates(processingParametersReporter_Configuration, 2);
		flagErrorsSettings = true;
	}

	//Check for the integration files
	if (!flagErrorsSettings && dataTemplates.length > 0) {
		processingParametersReporter_Configuration.setDataTemplates(dataTemplates);
		dataTemplates = processingParametersReporter_Configuration.filterTemplateByDimension(aDimCount);
		if (!(new File(dataTemplates[currentTemplate].path).exists)) {
			resultDefaultTemplates = this.getDefaultTemplates(processingParametersReporter_Configuration, aDimCount);
			flagErrorsSettings = true;
		}
	}

	//Mapping the vars to the currents ones
	if (flagErrorsSettings) {
		dataTemplates = resultDefaultTemplates[0];
		currentTemplate = resultDefaultTemplates[1];
	}

	//Choose the proper template file by using the dimension parameter
	templateFile = dataTemplates[currentTemplate].path;

	//Check is we are processing HTML code
	isHTML = this.checkHTMLCode(new Template(templateFile).readTemplate());

	//Process all the macros which belongs to the proper template
	macrosResult = this.processTemplate(aNMRProcessing, aSpec, templateFile, isHTML);

	//Assign the evaluation to a result var object
	result = {};

	if (aCreateItem) {
		textItem = draw.text(macrosResult, "Report Special", "NMR Processing," + aDimCount.toString(), isHTML);
		mainWindow.activeWindow().update();
		mainWindow.activeDocument.curPage().update();
	}

	//Return the results
	if (isHTML) {
		result.html = macrosResult;
	} else {
		result.plainText = macrosResult;
	}

	return result;
};
